﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Threading;

namespace CloudShot.Utils.Encoding
{
  //TODO: use async/await 
  //TODO: extract interface
  //TODO: cancel recording in destructor, make it IDisposable
  public class FastEncodingWorker
  {
    private readonly object _queueLock = new object();

    private readonly Queue<Bitmap> _drawingQueue = new Queue<Bitmap>();
    private readonly BackgroundWorker _worker = new BackgroundWorker();
    private readonly FastAnimatedGifEncoder _encoder;

    // Keyword volatile is used as a hint to the compiler that this data 
    // member is accessed by multiple threads. 
    private volatile bool _isFinishedRecording;

    public event Action<Image> FinishEncoding;
    public event Action<int> ProgressChanged;

    public int FramesCount { get; private set; }

    //TODO: maybe add StartRecording, ResetRecording methods?
    public FastEncodingWorker(int fps, int quality = 100)
    {
      _worker.WorkerReportsProgress = true;
      _worker.WorkerSupportsCancellation = true;

      _worker.DoWork += OnGifCreating;
      _worker.ProgressChanged += OnGifCreatingProgressChanged;
      _worker.RunWorkerCompleted += OnGifCreated;

      _encoder = new FastAnimatedGifEncoder
      {
        FPS = fps,
        Quality = quality
      };

      _encoder.Start();
      _worker.RunWorkerAsync();
    }

    public void FinishRecording()
    {
      _isFinishedRecording = true;
    }

    public void Cancel()
    {
      if (_worker.IsBusy)
        _worker.CancelAsync();
    }

    public void AddImage(Bitmap image)
    {
      lock (_queueLock)
      {
        _drawingQueue.Enqueue(image);
        FramesCount++;
      }
    }

    protected virtual void InvokeProgressChanged(int obj)
    {
      Action<int> handler = ProgressChanged;
      if (handler != null)
        handler(obj);
    }

    protected virtual void InvokeFinishEncoding(Image image)
    {
      Action<Image> handler = FinishEncoding;

      if (handler != null)
        handler(image);
    }

    private void OnGifCreating(object sender, DoWorkEventArgs e)
    {
      while (!_isFinishedRecording)
      {
        if (_worker.CancellationPending)
        {
          e.Cancel = true;
          return;
        }

        Bitmap img = null;

        int imageLeftCount;
        lock (_queueLock)
        {
          imageLeftCount = _drawingQueue.Count;

          if (imageLeftCount > 0)
            img = _drawingQueue.Dequeue();
        }

        if (img != null)
        {
          _encoder.AddFrame(img);
          _worker.ReportProgress(FramesCount - imageLeftCount);
        }
        else
        {
          Thread.Sleep(1);
        }
      }
    }

    private void OnGifCreatingProgressChanged(object sender, ProgressChangedEventArgs e)
    {
      InvokeProgressChanged(e.ProgressPercentage);
    }

    private void OnGifCreated(object sender, RunWorkerCompletedEventArgs e)
    {
      Image image = null;

      if (!e.Cancelled && FramesCount > 0)
        image = _encoder.Finish();

      InvokeFinishEncoding(image);
    }
  }
}